﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;

using TileMapEditor;
using TileMapEditor.Internal;
using TileMapEditor.Config;
using TileMapEditor.IO;


namespace TileMapEditor.Core
{
	public class TileMapPainter : TileMapUtilityWindow
	{
	    static TileMapPainter _instance;
	    public static TileMapPainter Instance
	    {
	        get
	        {
	            if ( null == _instance )
	            {
	                _instance = new TileMapPainter();
	            }
	
	            return _instance;
	        }
	    }
	
	    [SerializeField]
	    private static TileMapBrush _brush = null;
	
	    private int _step = 4;
	
	    private Vector2 _stride = new Vector2( 16, 16 );
	    public Vector2 stride
	    {
	        get { return _stride; }
	        set
	        {
	            _stride = value;
	            if ( null != StrideChangeCallback )
	            {
	                StrideChangeCallback.Invoke( _stride );
	            }
	        }
	    }
	
	    public Action<Vector2> StrideChangeCallback;
	
	    private SpriteRect _selected;
        private SpriteRectCache _rects_cache;

	    float sliderValue = 0.5f;
	
	    private bool _texture_is_dirty;
	    public bool textureIsDirty
	    {
	        get { return _texture_is_dirty; }
	        set { _texture_is_dirty = value; }
	    }

        private bool _active_texture_selected
        {
            get { return _texture != null; }
        }
	
	    private List<Vector2> _record_position;
	
	    private Rect inspectorRect
	    {
	        get
	        {
	            return new Rect( (float) ( (double) this.position.width - _inspector_width - _inspector_window_margin - _scrollbar_margin ),
	                (float) ( (double) this.position.height - _inspector_height - _inspector_window_margin - _scrollbar_margin ),
	                _inspector_width, _inspector_height );
	        }
	    }
	
	    #region Data
	
	    private bool _show_marked_region = true;
	
	    protected Dictionary<Vector2, int> _marked_region = new Dictionary<Vector2, int>();
	    protected Dictionary<Vector2, int> markedRegion
	    {
	        get { return _marked_region; }
	    }
	
	    private string _save_file_name
	    {
	        get
	        {
	            if ( null == _texture )
	                return null;
	
	            return Application.dataPath + "/" + _texture.name;
	        }
	    }
	
	    #endregion End Data
	
	    #region Path Finding
	
	    private PathFinding _path_finding;
	    private Vector2 _pf_start;
	    private Vector2 _pf_end;

        #endregion

        public static void ShowWindow()
	    {
	        var tile_map_editor = EditorWindow.GetWindow<TileMapPainter>();
	        tile_map_editor.titleContent = TileMapPainterStyles.titleLabel;
	        tile_map_editor.Show();
	    }
	
	    private void OnEnable()
	    {
	        wantsMouseMove = _wants_mouse_move;
            ResetPrewviewPath();

            Selection.selectionChanged += OnSelectionChanged;

            if ( null == _brush )
	        {
	            _brush = ScriptableObject.CreateInstance<TileMapBrush>();
	        }
	
	        _brush.stride = this.stride;
	        _brush.OnPaintCurrentRegion += SetPaintedRegion;
	
	        StrideChangeCallback += _brush.SetBrushStride;
	
	        _record_position = new List<Vector2>();
	    }
	
	    private void OnDisable()
	    {
            Selection.selectionChanged -= OnSelectionChanged;

            _brush.OnPaintCurrentRegion -= SetPaintedRegion;
	
	        StrideChangeCallback -= _brush.SetBrushStride;
	
	        _record_position.Clear();
	
	        //_brush = null;
	        _instance = null;
	    }
	
	    private void OnSelectionChanged()
	    {
            // clear the old marked region.
            markedRegion.Clear();

            _texture = GetSelectedTexture2D();
            if ( null != _texture )
            {
                RefreshTextureData();

                _show_marked_region = true;
                TileMapEditorIO.Load( _save_file_name, _size, ref _marked_region );

                Repaint();
            }
	    }
	
	    private void OnGUI()
	    {
            if ( ! _active_texture_selected )
            {
                using ( new EditorGUI.DisabledScope( true ) )
                {
                    GUILayout.Label( TileMapPainterStyles.noTextureLabel, new GUILayoutOption[0] );
                }
            }
            else
            {
                if ( IsMouseInner( _slot_position )
                    && Event.current.type == EventType.MouseDrag
                    && _brush.paintType != TileMapSettings.areaType[TileMapIds.npc] )
                {
                    if ( Event.current.button == 0 )
                    {
                        _brush.PaintCurrentRegion();
                        _brush.ClearCoveredRegion();

                        Repaint();
                    }
                    else if ( Event.current.button == 1 )
                    {
                        //Debug.Log( "Right Mouse Click" );
                    }
                    else
                    {
                        //Debug.Log( "Other Mouse Click" );
                    }
                }

                if ( _brush.paintType == TileMapSettings.areaType[TileMapIds.npc]
                    && Event.current.type == EventType.MouseDown
                    && Event.current.button == 0 )
                {
                    _brush.PaintCurrentRegion();
                    _brush.ClearCoveredRegion();

                    Repaint();
                }

                base.InitStyles();
                Rect rect = EditorGUILayout.BeginHorizontal( "Toolbar", new GUILayoutOption[0] );

                this._texture_view_rect = new Rect( 0f, rect.yMax, position.width - 16f, position.height - 16f - rect.height );
                GUILayout.FlexibleSpace();
                DoTextureGUI();

                EditorGUILayout.EndHorizontal();

                DrawTileMapInspector();
            }

	        if ( Event.current.type == EventType.MouseMove )
	        {
	            Repaint();
	        }
	    }
	
	    #region Drawing UI
	
	    /// <summary>
	    /// Draw inspector
	    /// </summary>
	    private void DrawTileMapInspector()
	    {
	        EditorGUIUtility.wideMode = true;
	
	        float label_width = EditorGUIUtility.labelWidth;
	        EditorGUIUtility.labelWidth = 135f;
	
	        GUILayout.BeginArea( this.inspectorRect );
	        GUILayout.BeginVertical( TileMapPainter.TileMapPainterStyles.titleLabel, GUI.skin.window, new GUILayoutOption[0] );
	
	        DrawBasicInfo();
	        DrawStrideField();
	
	        _brush.OnGUI();
	
	        DrawMarkedRegionSettings();
	
	        GUILayout.FlexibleSpace();
	
	        GUILayout.BeginHorizontal();
	        DrawClearButton();
	        DrawSaveButton();
	        GUILayout.EndHorizontal();
	
	        //PrintMarkedRegion();
	
	        GUILayout.EndVertical();
	        GUILayout.EndArea();
	    }
	
	    private void DrawBasicInfo()
	    {
	        EditorGUI.BeginChangeCheck();
	
	        EditorGUILayout.LabelField( "Basic Information", EditorStyles.boldLabel );
	
	        //GUI.SetNextControlName( "SpriteName" );
	
	        EditorGUI.BeginDisabledGroup( true );
	
	        // Texture name
	        var filename = EditorGUILayout.TextField( TileMapPainter.TileMapPainterStyles.nameLabel, _texture.name, new GUILayoutOption[0] );
	
	        EditorGUILayout.Vector2Field( TileMapPainterStyles.textureResolutionLabel, new Vector2( _texture.width, _texture.height ) );
	
	        // Zoom
	        _zoom = EditorGUILayout.FloatField( TileMapPainter.TileMapPainterStyles.zoomLabel, _zoom );
	
	        // Mouse Position
	        EditorGUILayout.Vector2Field( TileMapPainter.TileMapPainterStyles.coordinateLabel, _mouse_position, new GUILayoutOption[0] );
	
	        // Grid Coordinate
	        EditorGUILayout.Vector2Field( TileMapPainter.TileMapPainterStyles.gridCoordinateLabel, _slot_position, new GUILayoutOption[0] );
	
	        EditorGUI.EndDisabledGroup();
	        EditorGUI.EndChangeCheck();
	    }
	
	    private void DrawStrideField()
	    {
	        EditorGUI.BeginChangeCheck();
	
	
	        EditorGUI.BeginDisabledGroup( true );
	        //_step = EditorGUILayout.IntSlider( TileMapPainter.TileMapPainterStyles.strideExponentLabel, _step, 3, 6, new GUILayoutOption[0] );
	
	        //stride = new Vector2() { x = (int) Mathf.Pow( 2, _step ), y = stride.x };
	
	        stride = EditorGUILayout.Vector2Field( TileMapPainter.TileMapPainterStyles.strideLabel, stride, new GUILayoutOption[0] );
	        EditorGUI.EndDisabledGroup();
	
	        EditorGUI.EndChangeCheck();
	    }
	
	    protected override void DrawGrids()
	    {
	        TileMapEditorUtility.BeginLines( TileMapSettings.GridLineColor );
	
	        // Draw x-axis lines
	        for ( float i = 0; i <= _texture.height; i += stride.x )
	        {
	            GL.Vertex3( 0, i, 0 );
	            GL.Vertex3( _texture.width, i, 0 );
	        }
	
	        // Draw y-axis lines
	        for ( float j = 0; j <= _texture.width; j += stride.y )
	        {
	            GL.Vertex3( j, 0, 0 );
	            GL.Vertex3( j, _texture.height, 0 );
	        }
	
	        TileMapEditorUtility.EndLines();
	    }
	
	    protected override void DrawPreviewOverlayArea()
	    {
	        _brush.DrawBrush( _slot_position );
	    }
	
	    protected override void DrawMarkedRegion()
	    {
	        if ( _show_marked_region )
	        {
	            foreach ( var slot in markedRegion )
	            {
	                _brush.DrawPixel( slot.Key, slot.Value, false );
	            }
	        }
	    }
	
	    protected void DrawClearButton()
	    {
	        if ( GUILayout.Button( "Clear Marked Region" ) )
	        {
	            if ( markedRegion.Count != 0 &&
	                EditorUtility.DisplayDialog( "Warning", TileMapPainterStyles.confirmClearLabel, "Yes", "No" ) )
	            {
	                markedRegion.Clear();
	            }
	        }
	    }
	
	    protected void DrawSaveButton()
	    {
	        if ( GUILayout.Button( "Save" ) )
	        {
	            if ( markedRegion.Count == 0 )
	            {
	                Debug.Log( "You have nothing to save." );
	            }
	
	            TileMapEditorIO.Save( _save_file_name, markedRegion, _size );
	        }
	    }

        #endregion End Drawing UI

        #region Help Functions

        private Texture2D GetSelectedTexture2D()
        {
            Texture2D texture = null;
            if ( Selection.activeObject is Texture2D )
            {
                texture = ( Selection.activeObject as Texture2D );
            }
            else if ( Selection.activeObject is Sprite )
            {
                texture = UnityEditor.Sprites.SpriteUtility.GetSpriteTexture( Selection.activeObject as Sprite, false );
            }
            else if ( Selection.activeGameObject )
            {
                if ( Selection.activeGameObject.GetComponent<SpriteRenderer>() )
                {
                    if ( Selection.activeGameObject.GetComponent<SpriteRenderer>().sprite )
                    {
                        texture = UnityEditor.Sprites.SpriteUtility.GetSpriteTexture( Selection.activeGameObject.GetComponent<SpriteRenderer>().sprite, false );
                    }
                }
            }

            return texture;
        }

        protected void RefreshTextureData()
	    {
	        _size.x = Mathf.CeilToInt( _texture.width / stride.x );
	        _size.y = Mathf.CeilToInt( _texture.height / stride.y );
	    }
	
	    protected override void CalcRelativeMousePosition()
	    {
	        _mouse_position = new Vector2() {
	            x = ( Event.current.mousePosition.x - _texture_rect.xMin ) / _zoom,
	            y = ( _texture_rect.yMax - Event.current.mousePosition.y ) / _zoom
	        };
	
	        _slot_position = new Vector2() {
	            x = Mathf.FloorToInt( _mouse_position.x / stride.x ),
	            y = Mathf.FloorToInt( _mouse_position.y / stride.y )
	        };
	    }
	
	    protected void PrintMarkedRegion()
	    {
	        if ( GUILayout.Button( "Print Marked Region" ) )
	        {
	            foreach ( var slot in markedRegion )
	            {
	                Debug.LogFormat( "{0}: {1}", slot.Key.ToString(), slot.Value.ToString() );
	            }
	        }
	    }
	
	    #endregion End Help Functions
	
	
	    #region Set Marked Region
	
	    private void DrawMarkedRegionSettings()
	    {
	        EditorGUILayout.LabelField( TileMapPainterStyles.markedRegionLabel, EditorStyles.boldLabel );
	
	        EditorGUILayout.BeginVertical();
	
	        //_show_marked_region = GUILayout.Toggle( _show_marked_region, TileMapPainterStyles.toggleMarkedRegion );
	        _show_marked_region = EditorGUILayout.Toggle( TileMapPainterStyles.toggleMarkedRegion, _show_marked_region );
	
	        DrawPathFinding();
	
	        EditorGUILayout.EndVertical();
	    }
	
	    private void SetPaintedRegion( Dictionary<Vector2, int> slots )
	    {
	        foreach ( var slot in slots )
	        {
	            if ( markedRegion.ContainsKey( slot.Key ) )
	            {
	                markedRegion[slot.Key] = slot.Value;
	                //Debug.LogFormat( "Rewrite: {0}, {1}", slot.Key, slot.Value );
	            }
	            else
	            {
	                markedRegion.Add( slot.Key, slot.Value );
	            }
	        }
	    }
	
	    #endregion End Set Marked Region
	
	    #region PathFinding
	
	    private void DrawPathFinding()
	    {
	        EditorGUILayout.LabelField( new GUIContent( "Path Finding" ), EditorStyles.boldLabel );
	        EditorGUILayout.BeginVertical();
	
	        _pf_start = EditorGUILayout.Vector2Field( new GUIContent( "Start" ), _pf_start );
	        _pf_end = EditorGUILayout.Vector2Field( new GUIContent( "End" ), _pf_end );

            if ( GUILayout.Button( "A*" ) )
            {
                _path_finding = new PathFinding( markedRegion, (int) _size.x, (int) _size.y );
                _path_finding.DrawVisitedCallback -= DrawPreviewPath;
                _path_finding.DrawVisitedCallback += DrawPreviewPath;

                _path_finding.Astar( _pf_start, _pf_end );
            }

            if ( GUILayout.Button( "Stop A*" ) )
            {
                if ( null == _path_finding )
                    return;

                _path_finding.StopAstar();
                _path_finding.DrawVisitedCallback -= DrawPreviewPath;

                ResetPrewviewPath();
                Repaint();
            }

            EditorGUILayout.EndVertical();
	    }

        private void DrawPreviewPath( Vector2 pos, int area_type )
        {
            _preview_path_pos = pos;
            _preview_path_area_type = area_type;

            Repaint();
        }

        protected override void DrawPreviewFindingPath()
        {
            if ( _preview_path_area_type == 0xFF )
            {
                for ( int i = 0; i < _path_finding.visitedPos.Count; i++ )
                {
                    _brush.DrawPixel( _path_finding.visitedPos[i], _preview_path_area_type );
                }

                TileMapEditorUtility.BeginLines( Color.black );

                for ( int i = 0; i < _path_finding.optimizedPos.Count - 1; i++ )
                {
                    GL.Vertex3( _path_finding.optimizedPos[i].x * _stride.x + 8, _path_finding.optimizedPos[i].y * _stride.y + 8, 0 );
                    GL.Vertex3( _path_finding.optimizedPos[i+1].x * _stride.x + 8, _path_finding.optimizedPos[i+1].y * _stride.y + 8, 0 );
                }

                TileMapEditorUtility.EndLines();
            }
        }

        protected override void DrawNeaestRegion()
        {
            if ( null == _path_finding )
                return;

            for ( int i = 0; i < _path_finding.neaestRegion.Count; i++ )
            {
                _brush.DrawPixel( _path_finding.neaestRegion[i], 0xFF );
            }
        }

        #endregion

        private class TileMapPainterStyles
	    {
	        public static readonly GUIContent strideExponentLabel = EditorGUIUtilityWrapper.TextContent( "Stride Exponent" );
	        public static readonly GUIContent strideLabel = EditorGUIUtilityWrapper.TextContent( "Stride" );
	        public static readonly GUIContent changeShapeLabel = EditorGUIUtilityWrapper.TextContent( "Change Shape" );
	        public static readonly GUIContent sliceButtonLabel = EditorGUIUtilityWrapper.TextContent( "Size" );
	        public static readonly GUIContent sidesLabel = EditorGUIUtilityWrapper.TextContent( "Sides" );
	        public static readonly GUIContent editingDiableMessageLabel = EditorGUIUtilityWrapper.TextContent( "Editing is disabled during play mode" );
	        public static readonly GUIContent titleLabel = EditorGUIUtilityWrapper.TextContent( "Tile Map Editor" );
	        public static readonly GUIContent customPivotLabel = EditorGUIUtilityWrapper.TextContent( "Custom Pivot" );
	        public static readonly GUIContent borderLabel = EditorGUIUtilityWrapper.TextContent( "Border" );
	        public static readonly GUIContent positionLabel = EditorGUIUtilityWrapper.TextContent( "Position" );
	        public static readonly GUIContent nameLabel = EditorGUIUtilityWrapper.TextContent( "Name" );
	        public static readonly GUIContent zoomLabel = EditorGUIUtilityWrapper.TextContent( "Zoom" );
	        public static readonly GUIContent coordinateLabel = EditorGUIUtilityWrapper.TextContent( "Coordinate" );
	        public static readonly GUIContent textureResolutionLabel = EditorGUIUtilityWrapper.TextContent( "Resolution" );
	        public static readonly GUIContent gridCoordinateLabel = EditorGUIUtilityWrapper.TextContent( "Grid Coordinate" );
	        public static readonly GUIContent yesButtonLabel = EditorGUIUtilityWrapper.TextContent( "Yes" );
	        public static readonly GUIContent noButtonLabel = EditorGUIUtilityWrapper.TextContent( "No" );
	        public static readonly GUIContent okButtonLabel = EditorGUIUtilityWrapper.TextContent( "Ok" );
	        public static readonly GUIContent cancelButtonLabel = EditorGUIUtilityWrapper.TextContent( "Cancel" );
	        public static readonly GUIContent noTextureLabel = EditorGUIUtilityWrapper.TextContent( "No texture selected" );
	
	        public static readonly GUIContent markedRegionLabel = new GUIContent( "Marked Region Settings" );
	        public static readonly GUIContent toggleMarkedRegion = EditorGUIUtilityWrapper.TextContent( "Show Marked Region" );
	
	
	        public static readonly string confirmClearLabel = "Warning: The editing content not saved, do you really want to exit?";
	    }
	}
}
